package edu.hawaii.myisern.model;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import edu.hawaii.myisern.collaborations.jaxb.Collaboration;
import edu.hawaii.myisern.collaborations.jaxb.Collaborations;
import edu.hawaii.myisern.commandline.MyIsernXmlLoader;
import edu.hawaii.myisern.organizations.jaxb.Organization;
import edu.hawaii.myisern.organizations.jaxb.Organizations;
import edu.hawaii.myisern.researchers.jaxb.Researcher;
import edu.hawaii.myisern.researchers.jaxb.Researchers;

/**
 * Implements a singleton MyISERN instance.
 * To prevent multi-threaded access from corrupting MyISERN, all operations on the
 * singleton MyIsernModel instance are synchronized.
 *
 * @author Ka Yee Leung
 * @author Ivan Wu
 * @author Chiao-Fen Zielinski-Liu
 * @author Michal Zielinski
 */
public class MyIsernModel {
  /** The singleton MyIsernModel instance. */
  private static MyIsernModel theInstance = new MyIsernModel();
  
  /** The XML loader. */
  private MyIsernXmlLoader myIsernXml;
  
  /** The database instance */
  private DerbyMyIsernStore derbyStore;
  
  /** The empty string */
  private String empty = "";
  
  /** Private constructor used to create a single instance of MyISERN. */ 
  private MyIsernModel() {
    try {
      // this.myIsernXml = new MyIsernXmlLoader();
      this.derbyStore = new DerbyMyIsernStore();
    }
    catch (Exception error) {
      error.printStackTrace();
    }
  }
  
  
 
  /**
   * Get the single instance of the MyIsernModel object.
   *
   * @return A MyIsernModel.
   */
  public static synchronized MyIsernModel getInstance() {
    return MyIsernModel.theInstance;
  }
  
  /**
   * Creates and returns a list object representation of a string.
   * 
   * @param listString  The string that will be converted to a list.
   * @return The list object version of the string.
   */
  public synchronized List<String> createList(String listString) {
    List<String> list = new ArrayList<String>();
    if (listString == null) {
      return list;
    }
    
    String copy = listString;
    String part = "";

    while (copy.contains(",")) {
      int stopIndex = copy.indexOf(',');
      part = copy.substring(0, stopIndex);
      copy = copy.substring(stopIndex + 1);
      list.add(part.trim());
    }
    list.add(copy);
    
    return list;
  }
  
  /**
   * Generates a string representation of a list.
   * 
   * @param listElements The list of elements that will be turned into a string.
   * @return The string representation of the list.
   */
  public synchronized String createListString(List<String> listElements) {
    int listSize = listElements.size();
    int listCount = 1;
    StringBuffer listString = new StringBuffer();
    for (String element : listElements) {
      if (listCount < listSize) {
        element += ", ";
      }
      listString.append(element);
      listCount++;
    }
    
    return listString.toString();
  }
  
  /**
   * Gets the iterator attribute of the researcher list.
   *
   * @return The researcher list iterator value.
   */
  public synchronized List<Researcher> getResearchers() {
    return myIsernXml.getResearchers().getResearcher();
  }
  /**
   * Gets the iterator attribute of the researcher list.
   *
   * @return The researcher list iterator value.
   */
  public synchronized List<Researcher> getResearchersDB() {  
    return derbyStore.getResearchers().getResearcher();
  }
  
  /**
   * Adds a researcher.
   * 
   * @param newResearcher The researcher that will be added.
   * @throws Exception If problems occur.
   */
  public synchronized void addResearcher(Researcher newResearcher) throws Exception {
    myIsernXml.getResearchers().getResearcher().add(newResearcher);
    String newResearchersXml = myIsernXml.getResearchersXml();
    File researchersFile = new File(myIsernXml.getRsearchersFilePath());
    FileOutputStream fos = new FileOutputStream(researchersFile);
    fos.write(newResearchersXml.getBytes());
    fos.close();
  }
  
  /**
   * Removes the researcher.
   * 
   * @param name The name of the researcher that will be removed.
   * @throws Exception If problems occur.
   */
  public synchronized void removeResearcherDB(String name) throws Exception {
   derbyStore.deleteResearcher(name);
  }
  
  /**
   * Gets the iterator attribute of the organization list.
   *
   * @return The organization list iterator value.
   */
  public synchronized List<Organization> getOrganizations() {
  //myIsernXmlLoaderF
    return myIsernXml.getOrganizations().getOrganization();
  }
  
  /**
   * Gets the iterator attribute of the organization list.
   *
   * @return The researcher list iterator value.
   */
  public synchronized List<Organization> getOrganizationsDB() {  
    return derbyStore.getOrganizations().getOrganization();
  }
  
  /**
   * Adds a organization.
   * 
   * @param newOrganization The organization that will be added.
   * @throws Exception If problems occur.
   */
  public synchronized void addOrganization(Organization newOrganization) throws Exception {
    myIsernXml.getOrganizations().getOrganization().add(newOrganization);
    String newOrganizationsXml = myIsernXml.getOrganizationsXml();
    File organizationsFile = new File(myIsernXml.getOrganizationsFilePath());
    FileOutputStream fos = new FileOutputStream(organizationsFile);
    fos.write(newOrganizationsXml.getBytes());
    fos.close();
  }
  
  /**
   * Edits a organization.
   * 
   * @param editOrganization The organization that will be edited.
   * @param count The number that will be calculated.
   * @throws Exception If problems occur.
   */
  public synchronized void editOrganization(int count, Organization editOrganization)
      throws Exception {
    myIsernXml.getOrganizations().getOrganization().set(count, editOrganization);
    String newOrganizationsXml = myIsernXml.getOrganizationsXml();
    File organizationsFile = new File(myIsernXml.getOrganizationsFilePath());
    FileOutputStream fos = new FileOutputStream(organizationsFile);
    fos.write(newOrganizationsXml.getBytes());
    fos.close();
  }
  
  /**
   * Removes the organization.
   * 
   * @param name The name of the organization that will be removed.
   * @throws Exception If problems occur.
   */
  public synchronized void removeOrganization(String name) throws Exception {
    Organization removeOrganization = new Organization();
    List<Organization> list = myIsernXml.getOrganizations().getOrganization();
    for (Organization organization : list) {
      if (name.equals(organization.getName())) {
        removeOrganization = organization;
      }
    }
    myIsernXml.getOrganizations().getOrganization().remove(removeOrganization);
    String newOrganizationsXml = myIsernXml.getOrganizationsXml();
    File organizationsFile = new File(myIsernXml.getOrganizationsFilePath());
    FileOutputStream fos = new FileOutputStream(organizationsFile);
    fos.write(newOrganizationsXml.getBytes());
    fos.close();
  }
  
  /**
   * Gets the iterator attribute of the collaboration list.
   *
   * @return The collaboration list iterator value.
   */
  public synchronized List<Collaboration> getCollaborations() {
    return myIsernXml.getCollaborations().getCollaboration();
  }
  
  /**
   * Adds a collaboration
   * 
   * @param newCollaboration The collaboration that will be added.
   * @throws Exception If problems occur.
   */
  public synchronized void addCollaboration(Collaboration newCollaboration) throws Exception {
    myIsernXml.getCollaborations().getCollaboration().add(newCollaboration);
    String newCollaborationsXml = myIsernXml.getCollaborationsXml();
    File collaborationsFile = new File(myIsernXml.getCollaborationsFilePath());
    FileOutputStream fos = new FileOutputStream(collaborationsFile);
    fos.write(newCollaborationsXml.getBytes());
    fos.close();
  }
  
  /**
   * Edits a collaboration.
   * 
   * @param editCollaboration The collaboration that will be edited.
   * @param count The number that will be calculated.
   * @throws Exception If problems occur.
   */
  public synchronized void editCollaboration(int count, Collaboration editCollaboration)
      throws Exception {
    myIsernXml.getCollaborations().getCollaboration().set(count, editCollaboration);
    String newCollaborationsXml = myIsernXml.getCollaborationsXml();
    File collaborationsFile = new File(myIsernXml.getCollaborationsFilePath());
    FileOutputStream fos = new FileOutputStream(collaborationsFile);
    fos.write(newCollaborationsXml.getBytes());
    fos.close();
  }
  
  /**
   * Removes the collaboration.
   * 
   * @param name The name of the collaboration that will be removed.
   * @throws Exception If problems occur.
   */
  public synchronized void removeCollaboration(String name) throws Exception {
    Collaboration removeCollaboration = new Collaboration();
    List<Collaboration> list = myIsernXml.getCollaborations().getCollaboration();
    for (Collaboration collaboration : list) {
      if (name.equals(collaboration.getName())) {
        removeCollaboration = collaboration;
      }
    }
    myIsernXml.getCollaborations().getCollaboration().remove(removeCollaboration);
    String newCollaborationsXml = myIsernXml.getCollaborationsXml();
    File collaborationsFile = new File(myIsernXml.getCollaborationsFilePath());
    FileOutputStream fos = new FileOutputStream(collaborationsFile);
    fos.write(newCollaborationsXml.getBytes());
    fos.close();
  }
  
  /**
   * Gets the sanity check results list from XmlLoader so that the ActionBean
   * can grab it.
   * @return the Sanity Check results list.
   */
  public synchronized List<String> getSanityCheckResultsList() {
    this.myIsernXml.sanityCheck();
    return this.myIsernXml.getSanityCheckResults();
  }
  
  /**
   * Inserts the new Researcher into the DB.
   * @param newResearcher the researcher to insert
   */
  public synchronized void addResearcherDB(Researcher newResearcher) {
    if (newResearcher.getBioStatement() == null) {
      newResearcher.setBioStatement(empty);
    }
    if (newResearcher.getEmail() == null) {
      newResearcher.setEmail(empty);
    }
    if (newResearcher.getPictureLink() == null) {
      newResearcher.setPictureLink(empty);
    }
    derbyStore.insertResearcher(newResearcher);
  }
  
  /**
   * Edits the existing Researcher in the DB.
   *
   * @param researcher the edited values
   */
  public synchronized void editResearcherDB(Researcher researcher) {
    if (researcher.getBioStatement() == null) {
      researcher.setBioStatement(empty);
    }
    if (researcher.getEmail() == null) {
      researcher.setEmail(empty);
    }
    if (researcher.getPictureLink() == null) {
      researcher.setPictureLink(empty);
    }
    derbyStore.editResearcher(researcher);
  }
  
  /**
   * Inserts the new Organization into the DB.
   * @param newOrganization the researcher to insert
   */
  public synchronized void addOrganizationDB(Organization newOrganization) {
    if (newOrganization.getType() == null) {
      newOrganization.setType(empty);
    }
    if (newOrganization.getContact() == null) {
      newOrganization.setContact(empty);
    }
    if (newOrganization.getCountry() == null) {
      newOrganization.setCountry(empty);
    }
    if (newOrganization.getResearchDescription() == null) {
      newOrganization.setResearchDescription(empty);
    }
    if (newOrganization.getHomePage() == null) {
      newOrganization.setHomePage(empty);
    }
    derbyStore.insertOrganization(newOrganization);
  }
  
  /**
   * Edits the existing Organization in the DB.
   * @param organization the edited values
   */
  public synchronized void editOrganizationDB(Organization organization) {
    if (organization.getType() == null) {
      organization.setType(empty);
    }
    if (organization.getContact() == null) {
      organization.setContact(empty);
    }
    if (organization.getCountry() == null) {
      organization.setCountry(empty);
    }
    if (organization.getResearchDescription() == null) {
      organization.setResearchDescription(empty);
    }
    if (organization.getHomePage() == null) {
      organization.setHomePage(empty);
    }
    derbyStore.editOrganization(organization); 
  }
  
  /**
   * Removes the organization.
   * 
   * @param name The name of the organization that will be removed.
   * @throws Exception If problems occur.
   */
  public synchronized void removeOrganizationDB(String name) throws Exception {
   derbyStore.deleteOrganization(name);
  }
  
  /**
   * Gets the iterator attribute of the collaborations list.
   *
   * @return The collaboration list iterator value.
   */
  public synchronized List<Collaboration> getCollaborationsDB() {  
    return derbyStore.getCollaborations().getCollaboration();
  }
  
  /**
   * Inserts the new collaboration into the DB.
   * @param newCollaboration the collaboration to insert
   */
  public synchronized void addCollaborationDB(Collaboration newCollaboration) {
    if (newCollaboration.getDescription() == null) {
      newCollaboration.setDescription(empty);
    }
    derbyStore.insertCollaboration(newCollaboration);
  }
  
  /**
   * Edits the existing collaboration in the DB.
   * @param collaboration the edited values
   */
  public synchronized void editCollaborationDB(Collaboration collaboration) {
    if (collaboration.getDescription() == null) {
      collaboration.setDescription(empty);
    }
    derbyStore.editCollaboration(collaboration);
  }
  
  /**
   * Removes the collaboration.
   * 
   * @param name The name of the collaboration that will be removed.
   * @throws Exception If problems occur.
   */
  public synchronized void removeCollaborationDB(String name) throws Exception {
    derbyStore.deleteCollaboration(name);
  }
  
  /**
   * Import XML files into data base tables.
   * 
   * @return imported Return true if import successful.
   * @throws Exception Throws any exception.
   */
  public synchronized boolean importToDB() throws Exception {
    this.myIsernXml = new MyIsernXmlLoader();
    Researchers researchers = this.myIsernXml.getResearchers();
    Researcher researcher = new Researcher();
    Organizations organizations = this.myIsernXml.getOrganizations();
    Organization organization = new Organization();
    Collaborations collaborations = this.myIsernXml.getCollaborations();
    Collaboration collaboration = new Collaboration();
    boolean imported = true;
    int i = 0;
    for (i = 0; i < researchers.getResearcher().size(); i ++) {
      researcher = researchers.getResearcher().get(i);
      try {
        this.derbyStore.insertResearcher(researcher);
      }
      catch (Exception e) {
        e.printStackTrace();
        imported = false;
      }
    }
    for (i = 0; i < organizations.getOrganization().size(); i ++) {
      organization = organizations.getOrganization().get(i);
      try {
        this.derbyStore.insertOrganization(organization);
      }
      catch (Exception e) {
        e.printStackTrace();
        imported = false;
      }
    }
    for (i = 0; i < collaborations.getCollaboration().size(); i ++) {
      collaboration = collaborations.getCollaboration().get(i);
      try {
        this.derbyStore.insertCollaboration(collaboration);
      }
      catch (Exception e) {
        e.printStackTrace();
        imported = false;
      }
    }
    return imported;
  }
  
  /**
   * Write a researcher XML file.
   * 
   * @param path The path that the file will be wrote to.
   * 
   * @throws Exception If problems occur. 
   */
  public synchronized void writeResearcherXML(String path) throws Exception {
    String newResearchersXml = derbyStore.getResearchersXml();
    File researchersFile = new File(path);
    FileOutputStream fos = new FileOutputStream(researchersFile);
    fos.write(newResearchersXml.getBytes());
    fos.close();
  }
  
  /**
   * Write a organization XML file.
   * 
   * @param path The path that the file will be wrote to.
   * 
   * @throws Exception If problems occur. 
   */
  public synchronized void writeOrganizationXML(String path) throws Exception {
    String newOrganizationsXml = derbyStore.getOrganizationsXml();
    File organizationsFile = new File(path);
    FileOutputStream fos = new FileOutputStream(organizationsFile);
    fos.write(newOrganizationsXml.getBytes());
    fos.close();
  }
  
  /**
   * Write a collaboration XML file.
   * 
   * @param path The path that the file will be wrote to.
   * 
   * @throws Exception If problems occur.
   */
  public synchronized void writeCollaborationXML(String path) throws Exception {
    String newCollaborationsXml = derbyStore.getCollaborationsXml();
    File collaborationsFile = new File(path);
    FileOutputStream fos = new FileOutputStream(collaborationsFile);
    fos.write(newCollaborationsXml.getBytes());
    fos.close();
  }
  
  
  /**
   * Export data from data base tables to XML files.
   * 
   * @return exported Return true if export successful.
   */
  public synchronized boolean exportToXML() {
    
    String collaborationsFilePath = System.getProperty("user.dir")
                                    + "/xml/exportXML/collaborations.xml";
    String organizationsFilePath = System.getProperty("user.dir")
                                    + "/xml/exportXML/organizations.xml";
    String researchersFilePath = System.getProperty("user.dir")
                                    + "/xml/exportXML/researchers.xml";
    boolean exported = true;
    try {
      this.writeResearcherXML(researchersFilePath);
    }
    catch (Exception e) {
      e.printStackTrace();
      exported = false;
    }
    try {
      this.writeOrganizationXML(organizationsFilePath);
    }
    catch (Exception e) {
      e.printStackTrace();
      exported = false;
    }
    try {
      this.writeCollaborationXML(collaborationsFilePath);
    }
    catch (Exception e) {
      e.printStackTrace();
      exported = false;
    }
    return exported;
  }
}
